package edu.cornell.cubooks.controller;

import java.util.Date;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import edu.cornell.cubooks.dao.BidDao;
import edu.cornell.cubooks.dao.PostingDao;
import edu.cornell.cubooks.dao.UserDao;
import edu.cornell.cubooks.domain.Posting;
import edu.cornell.cubooks.domain.User;
import edu.cornell.cubooks.exception.UserNotFoundException;
import edu.cornell.cubooks.listener.LoginListener;
import edu.cornell.cubooks.service.NotificationService;
import edu.cornell.cubooks.utils.CurrentUser;
import edu.cornell.cubooks.validator.UserValidator;

/**
 * User controller which handles all requests related to users.
 * 
 * @author CUBooks team
 *
 */
@Controller
@SessionAttributes(types = User.class)
public class UsersController {
	
	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LoginListener.class);	
	private UserDao userDao;	
	private PostingDao postingDao;
	private BidDao bidDao;
	
	@Autowired
	private AuthenticationManager _authenticationManager;
	@Autowired 
	private NotificationService notificationService;
	@Autowired
	private ReCaptcha reCaptcha;
		
	@RequestMapping("/user/index.html")
	public ModelMap indexHandler() {
    	return new ModelMap(userDao.getAll());
	}
	
	@SuppressWarnings("unchecked")
	@RequestMapping("/user/home.html")
	public void homeHandler(ModelMap model) {
		Set<Posting> postings = getPostingDao().getMine(CurrentUser.getUser());
		model.put("postings", postings);
		Set<Posting> myBiddenPostings = getPostingDao().getAllByUserBids(CurrentUser.getUser());
		model.put("myBiddenPostings", myBiddenPostings);
	}

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/user/show.html", method = RequestMethod.GET)
	public void showHandler(@RequestParam("id") int id, ModelMap model) {
		model.put("user", this.userDao.get(id));
	}
	
	@RequestMapping(value = "/user/delete.html", method = RequestMethod.GET)
	public String deleteHandler(@RequestParam("id") int id) {
		this.userDao.deleteById(id);
		LOG.info("User with userId: " + id + " deleted by admin at " + new Date());
		return "redirect:/user/";
	}

	@RequestMapping(value = "/user/add.html", method = RequestMethod.GET)
	public String addHandler(Model model) {
		User user = new User();
		String captcha = reCaptcha.createRecaptchaHtml(null, null);
		user.setCaptcha(captcha);		
		model.addAttribute(user);
		return "user/form";
	}
	
	@RequestMapping(value = "/user/edit.html", method = RequestMethod.GET)
	public String editHandler(@RequestParam("id") int id, Model model)  throws UserNotFoundException {
		User user = this.userDao.get(id);
		if (user == null) {
			throw new UserNotFoundException("User " + id + " not found.");
		}
		if(!CurrentUser.isAdmin()) {
			if(!CurrentUser.getUser().equals(user)) {
				return "redirect:/access_denied.jsp";
			}
		}
		model.addAttribute(user);		
		return "user/editform";
	}
	
	@RequestMapping(value = "/user/new-password.html", method = RequestMethod.POST)
	public String newPasswordSubmit(@ModelAttribute User user, BindingResult result, SessionStatus status) {

		UserValidator userValidator = new UserValidator(userDao);
		userValidator.validateNewPassword(user, result);
		if (result.hasErrors()) {
			return "user/resetpassword_error";
		}
		getUserDao().changePassword(user);
		LOG.info("New password set for user with userId: " + user.getId() + " at" + new Date());		
		return "user/resetpassword_success";
	}
	
	@RequestMapping(value = "/user/reset-password.html", method = RequestMethod.GET)
	public String resetPasswordHandler(
		@RequestParam("token") String token,
		@RequestParam("email") String email,
		Model model) {
		UserValidator userValidator = new UserValidator(userDao);
		User user = userDao.loadUserByEmail(email);
		if (!userValidator.validateResetPassword(user, token)) {
			return "user/resetpassword_error";
		}
		model.addAttribute(user);
		LOG.info("Password resetted for user with userId: " + user.getId() + " at" + new Date());
		return "user/resetpassword";
	}
	
	@RequestMapping(value = "/user/forgot-password.html", method = RequestMethod.GET)
	public String forgotPasswordHandler(Model model) {
		User user = new User();
		String captcha = reCaptcha.createRecaptchaHtml(null, null);
		user.setCaptcha(captcha);		
		model.addAttribute(user);
		return "user/forgotpassword";
	}
	
	@RequestMapping(value = "/user/instructionsent.html", method = RequestMethod.GET)
	public String instructionssentHandler(Model model) {
		return "user/instructionsent";
	}
	
	@RequestMapping(value = "/user/send-instructions.html", method = RequestMethod.POST)
	public String processInstructionsSubmit(
			HttpServletRequest req,
			@RequestParam("recaptcha_challenge_field") String challenge,
		    @RequestParam("recaptcha_response_field") String response,
			@ModelAttribute User user, BindingResult result, SessionStatus status) {
		
		ReCaptchaResponse reCaptchaResponse 
 			= reCaptcha.checkAnswer(req.getRemoteAddr(), challenge, response);
	
		UserValidator userValidator = new UserValidator(userDao);
		userValidator.validateForgotPassword(user, result, reCaptchaResponse);
		if (result.hasErrors()) {
			return "user/forgotpassword";
		} else {
			user = (User)((UserDetailsService)userDao).loadUserByUsername(user.getUsername());
			// create token
			String token = getUserDao().createToken(user);
			
			// send out email 
			getNotificationService().sendResetPasswordEmail(user.getEmailAddress(), token);
			
			status.setComplete();
			LOG.info("New password send to the user with userId: " + user.getId() + " through email at" + new Date());
			return "redirect:/user/instructionsent.html";
		}
	}
	
	@RequestMapping(value = "/user/editaccount.html", method = RequestMethod.POST)
	public String editAccountSubmit(@ModelAttribute User user, BindingResult result, SessionStatus status) {

		UserValidator userValidator = new UserValidator(userDao);
		userValidator.validateEditAccount(user, result);
		if (result.hasErrors()) {
			return "/user/editform";
		}
		if(CurrentUser.getUser().equals(user)) {
			this.userDao.updateWithPasswordEncryption(user);
		} else {
			this.userDao.update(user);
		}
		status.setComplete();
		LOG.info("User entry for userId: " + user.getId() + " edited at " + new Date());
		if(CurrentUser.isAdmin()) {
			return "redirect:/user/";
		}
		return "redirect:/user/home.html";
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public String processSubmit(     
		HttpServletRequest req,
		@RequestParam("recaptcha_challenge_field") String challenge,
	    @RequestParam("recaptcha_response_field") String response,
	    @ModelAttribute User user, BindingResult result, SessionStatus status) {
		
		 	ReCaptchaResponse reCaptchaResponse 
		 		= reCaptcha.checkAnswer(req.getRemoteAddr(), challenge, response);
			UserValidator userValidator = new UserValidator(userDao);
			
			userValidator.validate(user, result, reCaptchaResponse);
			if (result.hasErrors()) {
				return "/user/form";
			}
			else {
				final String username = user.getUsername();
				final String password = user.getPassword();
				user = this.userDao.save(user);
				status.setComplete();
				LOG.info("New User with userId: " + user.getId() + " added at " + new Date());
				authenticate(req, username, password);
				return "redirect:/user/home.html";
			}
	}
	
	@RequestMapping(value = "/user/request-seller.html", method = RequestMethod.GET)
	public String requestSellerHandler(Model model) {
		User user = new User();
		model.addAttribute(user);
		if(CurrentUser.isBuyer() && !CurrentUser.isSeller()) {
			return "user/requestseller";
		}
		return "redirect:/access_denied.jsp";
	}
	
	@RequestMapping(value = "/user/request-seller-success.html", method = RequestMethod.POST)
	public String requestSellerSubmit(@ModelAttribute User formUser,  BindingResult result, SessionStatus status) {
		UserValidator userValidator = new UserValidator(userDao);
		userValidator.validateRequestSeller(formUser, result);
		if (result.hasErrors()) {
			return "user/requestseller";
		} else {
			User user = CurrentUser.getUser();
			user.setRequestSeller(formUser.getRequestSeller());
			userDao.update(user);
			LOG.info("User with userId: " + user.getId() + " requested for seller account at" + new Date());
			return "user/requestseller_success";
		}
	}
	
	@RequestMapping(value = "/user/approve.html", method = RequestMethod.GET)
	public String approveSellerHandler(@RequestParam("id") int id) {
		User user = this.userDao.get(id);
		if(user.getRequestSeller() == 1){
			userDao.approveSeller(user);
			getNotificationService().sendSellerAccountApprovedEmail(user.getEmailAddress());
			LOG.info("Seller request approved for User with userId: " + user.getId() + " at" + new Date());
		}
		return "redirect:/user/";
	}
	
	//TODO: move to userDAO
	private void authenticate(HttpServletRequest request, final String username, final String password) {
		try {
			
			//User user = (User) loadUserByUsername(username);
			UsernamePasswordAuthenticationToken usernameAndPassword =
				new UsernamePasswordAuthenticationToken(username, password);
			
			final HttpSession session = request.getSession();
			session.setAttribute(
					AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY, 
					username);
 
			// Authenticate, just to be sure
			Authentication auth = getAuthenticationManager().authenticate(usernameAndPassword);
			
			// Place the new Authentication object in the security context.
			final SecurityContext securityContext = SecurityContextHolder.getContext();
			securityContext.setAuthentication(auth);
			session.setAttribute( HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, securityContext);

		} catch (AuthenticationException e) {
			SecurityContextHolder.getContext().setAuthentication(null);
			LOG.error("Login failed after registration. This should never happen: ", e);
		}
	}
	
	//setters and getters
	
	public PostingDao getPostingDao() {
		return postingDao;
	}
	
	@Autowired
	public void setPostingDao(PostingDao postingDao) {
		this.postingDao = postingDao;
	}

	public UserDao getUserDao() {
		return userDao;
	}
	
	@Autowired
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public BidDao getBidDao() {
		return bidDao;
	}
	
	@Autowired
	public void setBidDao(BidDao bidDao) {
		this.bidDao = bidDao;
	}

	 public AuthenticationManager getAuthenticationManager() {
	     return _authenticationManager;
	 }

	 public void setAuthenticationManager(
	         AuthenticationManager authenticationManager ) {
	     _authenticationManager = authenticationManager;
	 }

	public NotificationService getNotificationService() {
		return notificationService;
	}

	public void setNotificationService(NotificationService notificationService) {
		this.notificationService = notificationService;
	}
}